﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

using System.IO;
using System.Management;
using System.Runtime.InteropServices;
using Microsoft.HomeServer.Controls.QSM;
using qsmif;

namespace Microsoft.HomeServer.HomeServerConsoleTab.DuplicationInfo
{
    public partial class DiskUsagePanel : UserControl
    {
        private const string BACKUP_FRIENDLY_NAME = "Windows Home Server Computer Backup";

        private static readonly Color SharedColor = Color.FromArgb(249, 221, 14);
        private static readonly Color BackupColor = Color.FromArgb(91, 146, 248);
        private static readonly Color VssColor = Color.FromArgb(129, 154, 70);
        private static readonly Color OtherColor = Color.FromArgb(131, 0, 75);
        private static readonly Color SystemColor = Color.FromArgb(200, 203, 189);
        private static readonly Color FreeColor = Color.FromArgb(246, 246, 246);

        private string diskName;

        private long sharedSize;
        private long backupSize;
        private long vssSize;
        private long otherSize;
        private long systemSize;
        private long freeSize;

        public DiskUsagePanel()
        {
            InitializeComponent();
        }

        public event EventHandler CloseButtonClick;

        private void closeToolStripButton_Click(object sender, EventArgs e)
        {
            CloseButtonClick(this, e);
        }

        private string SizeString(long size)
        {
            double sz = size;
            string[] prefix = new string[] { string.Empty, "K", "M", "G", "T" };

            int i;
            for (i = 0; i < prefix.Length; i++)
            {
                if (sz < 1024)
                {
                    break;
                }
                sz /= 1024;
            }

            if (i == 0)
            {
                return string.Format("{0:n0} B", sz);
            }
            else
            {
                return string.Format("{0:n1} {1}B", sz, prefix[i]);
            }
        }

        private Guid pendingGuid = Guid.Empty;

        public void ShowDisk(Guid guid)
        {
            sharedSizeLabel.Text =
            backupSizeLabel.Text = 
            vssSizeLabel.Text = 
            otherSizeLabel.Text = 
            systemSizeLabel.Text = 
            freeSizeLabel.Text = string.Empty;

            diskNameToolStripLabel.Text = "Calculating...";

            if (backgroundWorker.IsBusy)
            {
                backgroundWorker.CancelAsync();
                pendingGuid = guid;
            }
            else
            {
                backgroundWorker.RunWorkerAsync(guid);
                graphPanel.Invalidate();
            }
        }

        private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            Guid guid = (Guid)e.Argument;

            sharedSize = 0;
            backupSize = 0;
            vssSize = 0;
            otherSize = 0;
            systemSize = 0;
            freeSize = 0;

            string backupPath = null;
            foreach (Object obj in new QSMClass().QueryObjects(QSM_OBJECT_TYPE.QSM_OT_FOLDER))
            {
                IQSMFolder folder = (IQSMFolder)obj;
                if (folder.FriendlyName == BACKUP_FRIENDLY_NAME)
                {
                    string path = folder.Path;
                    if (path.StartsWith(@"D:\") || path.StartsWith(@"d:\"))
                    {
                        backupPath = path.Substring(3);
                    }
                }
            }

            foreach (Disk disk in QSMMgr.GetDisks())
            {
                if (disk.Role != DiskRole.SystemDisk && disk.Role != DiskRole.StorageDisk)
                {
                    continue;
                }

                foreach (Object obj in disk.QsmDisk.QueryObjects(QSM_OBJECT_TYPE.QSM_OT_VOLUME))
                {
                    IQSMVolume volume = (IQSMVolume)obj;
                    if (volume.Status != QSM_OBJECT_STATUS.QSM_OS_HEALTHY)
                    {
                        continue;
                    }

                    if (volume.ObjectName == guid)
                    {
                        string location = volume.Path;
                        diskName = disk.Name;

                        int clusterSize = 0;
                        foreach (ManagementObject wmiVolume in new ManagementClass("Win32_Volume").GetInstances())
                        {
                            string name = (string)wmiVolume["Name"];
                            if (name != null && string.Compare(name, location, true) == 0)
                            {
                                clusterSize = (int)(UInt64)wmiVolume["BlockSize"];

                                foreach (ManagementObject wmiShadowStorage in new ManagementClass("Win32_ShadowStorage").GetInstances())
                                {
                                    if ((string)wmiShadowStorage["volume"] ==
                                        string.Format(
                                            "Win32_Volume.DeviceID=\"{0}\"",
                                            ((string)wmiVolume["DeviceID"]).Replace(@"\", @"\\")
                                            )
                                        )
                                    {
                                        vssSize = (long)(UInt64)wmiShadowStorage["AllocatedSpace"];
                                    }
                                }
                            }
                        }

                        sharedSize = GetDirectorySize(location + @"DE\shares", clusterSize, e);
                        if (e.Cancel) { return; }
                        if (string.Compare(location, @"D:\", true) == 0)
                        {
                            sharedSize += GetDirectorySize(location + @"shares", clusterSize, e);
                            if (e.Cancel) { return; }
                        }

                        if (backupPath != null)
                        {
                            backupSize = GetDirectorySize(location + @"DE\" + backupPath, clusterSize, e);
                            if (e.Cancel) { return; }
                            if (string.Compare(location, @"D:\", true) == 0)
                            {
                                backupSize += GetDirectorySize(@"D:\" + backupPath, clusterSize, e);
                                if (e.Cancel) { return; }
                            }
                        }

                        /* vssSize = volume.VSSSpace; /* does not work. */

                        if (disk.Role == DiskRole.SystemDisk)
                        {
                            systemSize = QSMMgr.GetOperatingSystemSize();
                        }

                        freeSize = volume.FreeSpace;
                        otherSize = volume.Size - (sharedSize + backupSize + vssSize + freeSize);
                        if (otherSize < 0)
                        {
                            otherSize = 0;
                        }
                    }
                }
            }
        }

        private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (pendingGuid == Guid.Empty)
            {
                diskNameToolStripLabel.Text = diskName;

                sharedSizeLabel.Text = SizeString(sharedSize);
                backupSizeLabel.Text = SizeString(backupSize);
                vssSizeLabel.Text = SizeString(vssSize);
                otherSizeLabel.Text = SizeString(otherSize);
                systemSizeLabel.Text = SizeString(systemSize);
                freeSizeLabel.Text = SizeString(freeSize);

                graphPanel.Invalidate();
            }
            else
            {
                Guid next = pendingGuid;
                pendingGuid = Guid.Empty;
                ShowDisk(next);
            }
        }

        private void graphPanel_Resize(object sender, EventArgs e)
        {
            graphPanel.Invalidate();
        }

        private void graphPanel_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            Pen blackPen = new Pen(Color.Black);

            g.Clear(graphPanel.BackColor);

            if (backgroundWorker.IsBusy)
            {
                g.DrawRectangle(blackPen, 0, 0, graphPanel.Width - 1, graphPanel.Height - 1);
                return;
            }

            long total = sharedSize + backupSize + vssSize + otherSize + systemSize + freeSize;
            if (total == 0)
            {
                g.DrawRectangle(blackPen, 0, 0, graphPanel.Width - 1, graphPanel.Height - 1);
                return;
            }

            int totalWidth = graphPanel.Width - 1;
            int offset = 0, width;

            width = (int)Math.Round((double)totalWidth * sharedSize / total);
            g.FillRectangle(new SolidBrush(SharedColor), offset, 0, width, graphPanel.Height);
            g.DrawRectangle(blackPen, offset, 0, width, graphPanel.Height - 1);
            offset += width;

            width = (int)Math.Round((double)totalWidth * backupSize / total);
            g.FillRectangle(new SolidBrush(BackupColor), offset, 0, width, graphPanel.Height);
            g.DrawRectangle(blackPen, offset, 0, width, graphPanel.Height - 1);
            offset += width;

            width = (int)Math.Round((double)totalWidth * vssSize / total);
            g.FillRectangle(new SolidBrush(VssColor), offset, 0, width, graphPanel.Height);
            g.DrawRectangle(blackPen, offset, 0, width, graphPanel.Height - 1);
            offset += width;

            width = (int)Math.Round((double)totalWidth * otherSize / total);
            g.FillRectangle(new SolidBrush(OtherColor), offset, 0, width, graphPanel.Height);
            g.DrawRectangle(blackPen, offset, 0, width, graphPanel.Height - 1);
            offset += width;

            width = (int)Math.Round((double)totalWidth * systemSize / total);
            g.FillRectangle(new SolidBrush(SystemColor), offset, 0, width, graphPanel.Height);
            g.DrawRectangle(blackPen, offset, 0, width, graphPanel.Height - 1);
            offset += width;

            width = (int)Math.Round((double)totalWidth * freeSize / total);
            g.FillRectangle(new SolidBrush(FreeColor), offset, 0, width, graphPanel.Height);
            g.DrawRectangle(blackPen, offset, 0, width, graphPanel.Height - 1);
            offset += width;

            if (offset >= graphPanel.Width)
            {
                g.DrawRectangle(blackPen, 0, 0, graphPanel.Width - 1, graphPanel.Height - 1);
            }
        }

        private const int NO_ERROR = 0;

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        private static extern UInt32 GetCompressedFileSize(string lpFileName, out UInt32 lpFileSizeHigh);

        private long GetSizeOnDisk(string filename)
        {
            UInt32 low, high;
            low = GetCompressedFileSize(filename, out high);

            if ((int)low == -1 && Marshal.GetLastWin32Error() != NO_ERROR)
            {
                return 0;
            }
            else
            {
                return (long)low + ((long)high << 32);
            }
        }

        private long GetDirectorySize(string dirname, int clusterSize, DoWorkEventArgs e)
        {
            long total = 0;

            try
            {
                foreach (string filename in Directory.GetFiles(dirname))
                {
                    if (backgroundWorker.CancellationPending)
                    {
                        e.Cancel = true;
                        return 0;
                    }
                    long size = GetSizeOnDisk(filename);
                    if (clusterSize != 0)
                    {
                        size = (size + clusterSize - 1) / clusterSize * clusterSize;
                    }
                    total += size;
                }
            }
            catch (Exception) { }

            try
            {
                foreach (string subdirname in Directory.GetDirectories(dirname))
                {
                    if (backgroundWorker.CancellationPending)
                    {
                        e.Cancel = true;
                        return 0;
                    }
                    if ((File.GetAttributes(subdirname) & FileAttributes.ReparsePoint) == 0)
                    {
                        total += GetDirectorySize(subdirname, clusterSize, e);
                    }
                }
            }
            catch (Exception) { }

            return total;
        }

        private void iconPanel_Paint(object sender, PaintEventArgs e)
        {
            Color color;
            if (sender == sharedIconPanel) { color = SharedColor; }
            else if (sender == backupIconPanel) { color = BackupColor; }
            else if (sender == vssIconPanel) { color = VssColor; }
            else if (sender == otherIconPanel) { color = OtherColor; }
            else if (sender == systemIconPanel) { color = SystemColor; }
            else if (sender == freeIconPanel) { color = FreeColor; }
            else { return; }

            Panel panel = (Panel)sender;
            e.Graphics.FillRectangle(new SolidBrush(color), 0, 0, panel.Width, panel.Height);
            e.Graphics.DrawRectangle(new Pen(Color.Black), 0, 0, panel.Width - 1, panel.Height - 1);
        }
    }
}